import atexit
import json
import shutil
import hashlib
import sys
import tkinter as tk
from tkinter import simpledialog
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import queue

import os
import time

import os


def get_relative_path(file_path, base_dir=None):
    """
    获取相对路径，支持跨盘符情况。
    如果 file_path 和 base_dir 在不同盘符，则返回原路径。
    """
    if base_dir is None:
        base_dir = os.path.abspath(os.sep)  # 默认为系统根目录

    try:
        # 判断是否在同一盘符
        file_drive = os.path.splitdrive(os.path.normpath(file_path))[0]
        base_drive = os.path.splitdrive(os.path.normpath(base_dir))[0]

        if file_drive.lower() != base_drive.lower():
            # 不同盘符，返回原路径或特殊格式（如 C_file\test.txt）
            drive_letter = file_drive.strip(':').strip('\\').strip('/')
            relative = os.path.relpath(file_path, start=file_drive + '\\')
            return os.path.join(drive_letter, relative)

        # 同盘符，正常计算相对路径
        return os.path.relpath(file_path, start=base_dir)

    except ValueError:
        # 捕获 relpath 异常作为兜底
        return file_path


def ensure_single_instance():
    """确保单例运行（Windows专用实现）"""
    import win32api
    import win32event
    # 创建互斥体
    mutex = win32event.CreateMutex(None, False, "lockfile_唯一实例")
    if win32api.GetLastError() == 183:  # ERROR_ALREADY_EXISTS
        print("检测到已有实例在运行")
        sys.exit(1)
    atexit.register(lambda: win32api.CloseHandle(mutex))
    return mutex

# 新增：开机自动启动实现
def add_to_startup():
    """通过创建启动项实现开机自动启动"""
    try:
        import winshell
        from win32com.client import Dispatch
    except ImportError:
        print("请安装依赖库：pip install pywin32 winshell")
        return

    startup_folder = winshell.startup()  # 获取启动目录
    print("添加到启动项：", startup_folder)
    shortcut_path = os.path.join(startup_folder, "lockfile.lnk")

    if not os.path.exists(shortcut_path):
        shell = Dispatch('WScript.Shell')
        shortcut = shell.CreateShortCut(shortcut_path)

        # 判断是否是打包后的exe环境
        if getattr(sys, 'frozen', False):
            # 打包后的exe路径
            executable_path = sys.executable
        else:
            # 开发环境，使用当前脚本路径
            executable_path = os.path.abspath(__file__)

        # 快捷方式直接指向 .exe 文件，无需额外参数
        shortcut.Targetpath = executable_path
        shortcut.Arguments = ''  # 因为目标已经是exe，不需要参数
        shortcut.WorkingDirectory = os.path.dirname(executable_path)  # 设置工作目录为exe所在目录
        shortcut.save()
        print("已添加到开机启动项")
    else:
        print("已存在开机启动项")

def load_config():
    """加载配置文件（支持 PyInstaller 打包环境）"""
    if getattr(sys, 'frozen', False):
        # 如果是打包后的 exe 文件，尝试从 sys._MEIPASS 和 当前目录查找 config.json
        base_path = sys._MEIPASS  # PyInstaller 解压资源的临时目录
        config_path = os.path.join(base_path, "config.json")

        # 如果 _MEIPASS 中没有找到，则尝试从 .exe 所在目录加载
        if not os.path.exists(config_path):
            base_path = os.path.dirname(sys.executable)
            config_path = os.path.join(base_path, "config.json")
    else:
        # 开发环境：从当前脚本所在目录加载
        base_path = os.path.dirname(__file__)
        config_path = os.path.join(base_path, "config.json")

    if not os.path.exists(config_path):
        raise Exception(f"配置文件 {config_path} 不存在，请确认是否缺少 config.json")

    try:
        with open(config_path, 'r', encoding='utf-8') as f:
            config = json.load(f)

        # 路径转换处理
        config["monitored_files"] = [os.path.abspath(f) for f in config["monitored_files"]]
        config["backup_dir"] = os.path.abspath(config["backup_dir"])

        return config
    except json.JSONDecodeError:
        raise Exception("配置文件格式错误，请检查 JSON 格式是否正确")
    except KeyError as e:
        raise Exception(f"配置文件缺少必要字段: {str(e)}")



# 加载配置
try:
    config = load_config()
    monitored_files = config["monitored_files"]
    backup_dir = config["backup_dir"]
    password_hash = config["password_hash"]
except Exception as e:
    print(f"配置加载失败: {str(e)}")
    exit(1)


def init_backup():
    """初始化备份文件"""
    if not os.path.exists(backup_dir):
        os.makedirs(backup_dir)
    for file_path in monitored_files:
        if os.path.exists(file_path):
            # 创建备份路径
            rel_path = get_relative_path(file_path)
            backup_path = os.path.join(backup_dir, rel_path)
            os.makedirs(os.path.dirname(backup_path), exist_ok=True)
            shutil.copy2(file_path, backup_path)
            # 保存哈希值
            with open(file_path, 'rb') as f:
                current_hash = hashlib.sha256(f.read()).hexdigest()
            with open(backup_path + '.hash', 'w') as f:
                f.write(current_hash)


def compute_hash(file_path):
    """计算文件哈希"""
    if not os.path.exists(file_path):
        return None
    with open(file_path, 'rb') as f:
        return hashlib.sha256(f.read()).hexdigest()


def get_backup_hash(file_path):
    """获取备份文件哈希"""
    rel_path = get_relative_path(file_path)
    backup_path = os.path.join(backup_dir, rel_path)
    hash_file = backup_path + '.hash'
    if os.path.exists(hash_file):
        with open(hash_file, 'r') as f:
            return f.read().strip()
    return None


def restore_file(file_path):
    """恢复文件到备份版本"""
    rel_path = get_relative_path(file_path)
    backup_path = os.path.join(backup_dir, rel_path)
    if os.path.exists(backup_path):
        shutil.copy2(backup_path, file_path)


def update_backup(file_path):
    """更新备份文件"""
    rel_path =get_relative_path(file_path)
    backup_path = os.path.join(backup_dir, rel_path)
    os.makedirs(os.path.dirname(backup_path), exist_ok=True)
    shutil.copy2(file_path, backup_path)
    with open(file_path, 'rb') as f:
        current_hash = hashlib.sha256(f.read()).hexdigest()
    with open(backup_path + '.hash', 'w') as f:
        f.write(current_hash)


class ProtectedFileHandler(FileSystemEventHandler):
    """文件系统事件处理器"""

    def __init__(self, task_queue):
        super().__init__()
        # 初始化时添加互斥锁
        self.mutex = ensure_single_instance()
        self.task_queue = task_queue
        self.last_processed = {}

    def on_modified(self, event):
        if not event.is_directory:
            file_path = os.path.abspath(event.src_path)
            if file_path in monitored_files:
                # 去抖处理：1秒内不重复处理
                if time.time() - self.last_processed.get(file_path, 0) < 1:
                    return
                self.last_processed[file_path] = time.time()

                current_hash = compute_hash(file_path)
                backup_hash = get_backup_hash(file_path)
                if current_hash != backup_hash:
                    self.task_queue.put(('modified', file_path))

    def on_deleted(self, event):
        if not event.is_directory:
            file_path = os.path.abspath(event.src_path)
            if file_path in monitored_files:
                self.task_queue.put(('deleted', file_path))


def show_auth_dialog():
    """显示授权对话框"""
    root = tk.Tk()
    root.withdraw()
    password = simpledialog.askstring(
        " ", "请输入授权码：", show='*'
    )
    return hashlib.sha256(password.encode()).hexdigest() == password_hash if password else False


from watchdog.events import FileSystemEventHandler

class ConfigFileHandler(FileSystemEventHandler):
    def __init__(self, reload_callback):
        self.reload_callback = reload_callback

    def on_modified(self, event):
        if not event.is_directory and os.path.basename(event.src_path) == "config.json":
            print("检测到 config.json 被修改，正在重新加载配置...")
            self.reload_callback()


def load_config_and_update():
    """重新加载配置并更新全局变量"""
    try:
        new_config = load_config()

        # 更新全局变量
        global monitored_files, backup_dir, password_hash
        monitored_files = new_config["monitored_files"]
        backup_dir = new_config["backup_dir"]
        password_hash = new_config["password_hash"]
        print("配置已更新。")
        init_backup()
    except Exception as e:
        print(f"配置重新加载失败: {str(e)}")


def main():
    init_backup()
    task_queue = queue.Queue()

    # 初始化文件监控
    observer = Observer()
    handler = ProtectedFileHandler(task_queue)

    # 为每个文件的所在目录添加监控
    directories = {os.path.dirname(f) for f in monitored_files}
    for d in directories:
        if os.path.exists(d):
            observer.schedule(handler, d, recursive=False)

    # 新增：监控 config.json 文件变化
    config_dir = os.path.dirname(os.path.abspath("config.json"))
    config_handler = ConfigFileHandler(reload_callback=load_config_and_update)
    observer.schedule(config_handler, config_dir, recursive=False)

    observer.start()

    try:
        while True:
            try:
                event_type, file_path = task_queue.get_nowait()
                if show_auth_dialog():
                    if event_type == 'modified':
                        update_backup(file_path)
                        print(f"授权成功：{file_path} 修改已保存")
                else:
                    if event_type == 'modified':
                        restore_file(file_path)
                        print(f"未授权操作：{file_path} 已恢复")
                    elif event_type == 'deleted':
                        restore_file(file_path)
                        print(f"未授权删除：{file_path} 已恢复")
            except queue.Empty:
                time.sleep(0.5)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()



if __name__ == "__main__":
    # 新增：开机启动功能调用
    add_to_startup()
    main()

# pyinstaller --onefile --noconsole --icon=保护.png --name "lockfile V 1.0.0"  main.py
# pyinstaller --onefile --noconsole --icon=保护.png    --add-data "config.json;."  --name "lockfile V 1.0.0" main.py